package com.tools.web.filter.gateway;

import com.tools.common.container.RowKit;
import com.tools.common.exception.IORuntimeException;
import com.tools.common.io.CharsetEnum;
import com.tools.common.io.IOKit;
import com.tools.common.io.ProfileKit;
import com.tools.common.object.Note;
import com.tools.common.thread.TimePair;
import com.tools.web.WebKit;
import com.tools.web.filter.cors.CorsSource;

import java.io.BufferedReader;
import java.io.File;
import java.io.IOException;
import java.util.HashSet;
import java.util.Map;
import java.util.Objects;
import java.util.Set;

/**
 * 网关过滤器配置项包装实例
 * */
@Note("网关过滤器配置项包装实例")
public class GatewaySource {

    @Note("response 的 ContentType。默认为 JSON 字符串")
    private String responseContentType;

    @Note("response 的默认字符集。默认为 UTF-8")
    private CharsetEnum responseCharset;

    @Note("【能直连的 IP 白名单】 的 key。不为空则开启")
    private String proxyWhitesCacheKey;

    @Note("【接口禁用列表】 的 key。不为空则开启")
    private String disableURICacheKey;

    @Note("【客户端 IP 白名单】 - 的 key。不为空则开启")
    private String ipWhitesCacheKey;

    @Note("【客户端 IP 黑名单】 - 的 key。不为空则开启")
    private String ipBlacksCacheKey;

    @Note("【拉黑模式的配置】 - 客户端 IP 黑名单功能启动后这个配置若不为 null 且 isEnabled 为 true 即生效")
    private AddBlacker addBlacker;

    @Note("【能直连的 IP 白名单】")
    private Set<String> proxyWhites;

    @Note("【接口禁用列表】")
    private Set<String> disableURIs;

    @Note("【客户端 IP 白名单】")
    private Set<String> ipWhites;

    @Note("【客户端 IP 黑名单】")
    private Set<String> ipBlacks;

    @Note("跨域配置，是否在网关开启跨域设置")
    private CorsSource corsSource;

    @Note("服务器上下文路径")
    private String serverContextPath;

    /* ********************************************************************************************
     *
     *         构造器
     *
     * *********************************************************************************************
     * */

    public GatewaySource() {
        this.responseContentType = "application/json";
        this.responseCharset = CharsetEnum.UTF_8;
    }

    public static GatewaySource of() {
        return new GatewaySource();
    }

    /* ********************************************************************************************
     *
     *         Getter / Setter
     *
     * *********************************************************************************************
     * */

    public String getResponseContentType() {
        return responseContentType;
    }

    public void setResponseContentType(String responseContentType) {
        if(responseContentType != null && responseContentType.contains("/")) {
            this.responseContentType = responseContentType;
        }
    }

    public CharsetEnum getResponseCharset() {
        return responseCharset;
    }

    public void setResponseCharset(CharsetEnum responseCharset) {
        if(responseCharset != null) {
            this.responseCharset = responseCharset;
        }
    }

    public String getProxyWhitesCacheKey() {
        return proxyWhitesCacheKey;
    }

    public void setProxyWhitesCacheKey(String proxyWhitesCacheKey) {
        this.proxyWhitesCacheKey = proxyWhitesCacheKey;
    }

    public String getDisableURICacheKey() {
        return disableURICacheKey;
    }

    public void setDisableURICacheKey(String disableURICacheKey) {
        this.disableURICacheKey = disableURICacheKey;
    }

    public String getIpWhitesCacheKey() {
        return ipWhitesCacheKey;
    }

    public void setIpWhitesCacheKey(String ipWhitesCacheKey) {
        this.ipWhitesCacheKey = ipWhitesCacheKey;
    }

    public String getIpBlacksCacheKey() {
        return ipBlacksCacheKey;
    }

    public void setIpBlacksCacheKey(String ipBlacksCacheKey) {
        this.ipBlacksCacheKey = ipBlacksCacheKey;
    }

    public AddBlacker getAddBlacker() {
        return addBlacker;
    }

    public void setAddBlacker(AddBlacker addBlacker) {
        this.addBlacker = addBlacker;
    }

    public Set<String> getProxyWhites() {
        return proxyWhites;
    }

    public void setProxyWhites(String proxyWhites) {
        this.proxyWhites = private_stringSplitToSet(proxyWhites);
    }

    public void setProxyWhites(String... proxyWhites) {
        this.proxyWhites = private_arrayToSet(proxyWhites);
    }

    public void setProxyWhites(Iterable<String> proxyWhites) {
        this.proxyWhites = private_iterableToSet(proxyWhites);
    }

    public void setProxyWhitesByFile(String filePath) {
        this.proxyWhites = private_readFileAsSet(filePath, null);
    }

    public Set<String> getDisableURIs() {
        return disableURIs;
    }

    public void setDisableURIs(String disableURIs) {
        this.private_forceDisabledURISet(private_stringSplitToSet(disableURIs));
    }

    public void setDisableURIs(String... disableURIs) {
        this.private_forceDisabledURISet(private_arrayToSet(disableURIs));
    }

    public void setDisableURIs(Iterable<String> disableURIs) {
        this.private_forceDisabledURISet(private_iterableToSet(disableURIs));
    }

    public void setDisableURIsByFile(String filePath) {
        this.private_forceDisabledURISet(private_readFileAsSet(filePath, null));
    }

    public Set<String> getIpWhites() {
        return ipWhites;
    }

    public void setIpWhites(String ipWhites) {
        this.ipWhites = private_stringSplitToSet(ipWhites);
    }

    public void setIpWhites(String... ipWhites) {
        this.ipWhites = private_arrayToSet(ipWhites);
    }

    public void setIpWhites(Iterable<String> ipWhites) {
        this.ipWhites = private_iterableToSet(ipWhites);
    }

    public void setIpWhitesByFile(String filePath) {
        this.ipWhites = private_readFileAsSet(filePath, "allow ");
    }

    public Set<String> getIpBlacks() {
        return ipBlacks;
    }

    public void setIpBlacks(String ipBlacks) {
        this.ipBlacks = private_stringSplitToSet(ipBlacks);
    }

    public void setIpBlacks(String... ipBlacks) {
        this.ipBlacks = private_arrayToSet(ipBlacks);
    }

    public void setIpBlacks(Iterable<String> ipBlacks) {
        this.ipBlacks = private_iterableToSet(ipBlacks);
    }

    public void setIpBlacksByFile(String filePath) {
        this.ipBlacks = private_readFileAsSet(filePath, "deny ");
    }

    public CorsSource getCorsSource() {
        return corsSource;
    }

    public void setCorsSource(CorsSource corsSource) {
        this.corsSource = corsSource;
    }

    public String getServerContextPath() {
        return serverContextPath;
    }

    public void setServerContextPath(String serverContextPath) {
        this.serverContextPath = serverContextPath;
    }

    @Override
    public String toString() {
        return "GatewaySource{" +
                "responseContentType=" + responseContentType +
                ", responseCharset=" + responseCharset +
                ", serverContextPath=" + serverContextPath +
                ", proxyWhitesCacheKey=" + proxyWhitesCacheKey +
                ", disableURICacheKey=" + disableURICacheKey +
                ", ipWhitesCacheKey=" + ipWhitesCacheKey +
                ", ipBlacksCacheKey=" + ipBlacksCacheKey +
                ", addBlacker=" + addBlacker +
                ", proxyWhites=" + proxyWhites +
                ", disableURIs=" + disableURIs +
                ", ipWhites=" + ipWhites +
                ", ipBlacks=" + ipBlacks +
                ", corsSource=" + corsSource +
                '}';
    }

    @Override
    public boolean equals(Object o) {
        if (this == o) return true;
        if (o == null || getClass() != o.getClass()) return false;
        GatewaySource that = (GatewaySource) o;
        return Objects.equals(responseContentType, that.responseContentType) && responseCharset == that.responseCharset && Objects.equals(serverContextPath, that.serverContextPath) && Objects.equals(proxyWhitesCacheKey, that.proxyWhitesCacheKey) && Objects.equals(disableURICacheKey, that.disableURICacheKey) && Objects.equals(ipWhitesCacheKey, that.ipWhitesCacheKey) && Objects.equals(ipBlacksCacheKey, that.ipBlacksCacheKey) && Objects.equals(addBlacker, that.addBlacker) && Objects.equals(proxyWhites, that.proxyWhites) && Objects.equals(disableURIs, that.disableURIs) && Objects.equals(ipWhites, that.ipWhites) && Objects.equals(ipBlacks, that.ipBlacks) && Objects.equals(corsSource, that.corsSource);
    }

    @Override
    public int hashCode() {
        return Objects.hash(responseContentType, responseCharset, serverContextPath, proxyWhitesCacheKey, disableURICacheKey, ipWhitesCacheKey, ipBlacksCacheKey, addBlacker, proxyWhites, disableURIs, ipWhites, ipBlacks, corsSource);
    }

    /* ********************************************************************************************
     *
     *         链式调用逻辑
     *
     * *********************************************************************************************
     * */

    public GatewaySource responseContentType(String responseContentType) {
        this.setResponseContentType(responseContentType);
        return this;
    }

    public GatewaySource responseCharset(CharsetEnum responseCharset) {
        this.setResponseCharset(responseCharset);
        return this;
    }

    public GatewaySource proxyWhitesCacheKey(String proxyWhitesCacheKey) {
        this.setProxyWhitesCacheKey(proxyWhitesCacheKey);
        return this;
    }

    public GatewaySource disableURICacheKey(String disableURICacheKey) {
        this.setDisableURICacheKey(disableURICacheKey);
        return this;
    }

    public GatewaySource ipWhitesCacheKey(String ipWhitesCacheKey) {
        this.setIpWhitesCacheKey(ipWhitesCacheKey);
        return this;
    }

    public GatewaySource ipBlacksCacheKey(String ipBlacksCacheKey) {
        this.setIpBlacksCacheKey(ipBlacksCacheKey);
        return this;
    }

    public GatewaySource addBlacker(AddBlacker addBlacker) {
        this.setAddBlacker(addBlacker);
        return this;
    }

    public GatewaySource proxyWhites(String proxyWhites) {
        this.setProxyWhites(proxyWhites);
        return this;
    }

    public GatewaySource proxyWhites(String... proxyWhites) {
        this.setProxyWhites(proxyWhites);
        return this;
    }

    public GatewaySource proxyWhites(Iterable<String> proxyWhites) {
        this.setProxyWhites(proxyWhites);
        return this;
    }

    public GatewaySource proxyWhitesByFile(String filePath) {
        this.setProxyWhitesByFile(filePath);
        return this;
    }

    public GatewaySource disableURIs(String disableURIs) {
        this.setDisableURIs(disableURIs);
        return this;
    }

    public GatewaySource disableURIs(String... disableURIs) {
        this.setDisableURIs(disableURIs);
        return this;
    }

    public GatewaySource disableURIs(Iterable<String> disableURIs) {
        this.setDisableURIs(disableURIs);
        return this;
    }

    public GatewaySource disableURIsByFile(String filePath) {
        this.setDisableURIsByFile(filePath);
        return this;
    }

    public GatewaySource ipWhites(String ipWhites) {
        this.setIpWhites(ipWhites);
        return this;
    }

    public GatewaySource ipWhites(String... ipWhites) {
        this.setIpWhites(ipWhites);
        return this;
    }

    public GatewaySource ipWhites(Iterable<String> ipWhites) {
        this.setIpWhites(ipWhites);
        return this;
    }

    public GatewaySource ipWhitesByFile(String filePath) {
        this.setIpWhitesByFile(filePath);
        return this;
    }

    public GatewaySource ipBlacks(String ipBlacks) {
        this.setIpBlacks(ipBlacks);
        return this;
    }

    public GatewaySource ipBlacks(String... ipBlacks) {
        this.setIpBlacks(ipBlacks);
        return this;
    }

    public GatewaySource ipBlacks(Iterable<String> ipBlacks) {
        this.setIpBlacks(ipBlacks);
        return this;
    }

    public GatewaySource ipBlacksByFile(String filePath) {
        this.setIpBlacksByFile(filePath);
        return this;
    }

    public GatewaySource corsSource(CorsSource corsSource) {
        this.setCorsSource(corsSource);
        return this;
    }

    public GatewaySource serverContextPath(String serverContextPath) {
        this.setServerContextPath(serverContextPath);
        return this;
    }

    /* ********************************************************************************************
     *
     *         读取配置生成的方法
     *
     * *********************************************************************************************
     * */

    @Note("读取 properties 里的配置项，转为实例返回")
    public static GatewaySource from(Map properties) {
        GatewaySource source = new GatewaySource();
        if(properties == null || properties.isEmpty()) return source;
        source.setResponseContentType((String) properties.get("gateway-manager-responseContentType"));
        String charset = (String) properties.get("gateway-manager-responseCharset");
        source.setResponseCharset(CharsetEnum.getEnumSafe(charset));
        source.setProxyWhitesCacheKey((String) properties.get("gateway-manager-proxyWhitesCacheKey"));
        source.setDisableURICacheKey((String) properties.get("gateway-manager-disableURICacheKey"));
        source.setIpWhitesCacheKey((String) properties.get("gateway-manager-ipWhitesCacheKey"));
        source.setIpBlacksCacheKey((String) properties.get("gateway-manager-ipBlacksCacheKey"));
        String saveKeyPrefix = (String) properties.get("gateway-manager-addBlacker-saveKeyPrefix");
        String thresholdTimeString = (String) properties.get("gateway-manager-addBlacker-thresholdTime");
        Integer threshold = (Integer) properties.get("gateway-manager-addBlacker-threshold");
        TimePair thresholdTime = TimePair.buildSafe(thresholdTimeString);
        if(saveKeyPrefix != null && !saveKeyPrefix.isEmpty()
                && thresholdTime != null && thresholdTime.isEffectiveTime()
                && threshold != null && threshold > 0) {
            source.setAddBlacker(new AddBlacker(saveKeyPrefix, thresholdTime, threshold));
        }
        source.setProxyWhites((String) properties.get("gateway-manager-init-proxyWhites"));
        source.setProxyWhitesByFile((String) properties.get("gateway-manager-init-proxyWhitesFilePath"));
        source.setDisableURIs((String) properties.get("gateway-manager-init-disableURIs"));
        source.setDisableURIsByFile((String) properties.get("gateway-manager-init-disableURIsFilePath"));
        source.setIpWhites((String) properties.get("gateway-manager-init-ipWhites"));
        source.setIpWhitesByFile((String) properties.get("gateway-manager-init-ipWhitesFilePath"));
        source.setIpBlacks((String) properties.get("gateway-manager-init-ipBlacks"));
        source.setIpBlacksByFile((String) properties.get("gateway-manager-init-ipBlacksFilePath"));
        Boolean enabledCorsFilter = (Boolean) properties.get("gateway-manager-corsFilter-enabled");
        if(enabledCorsFilter != null && enabledCorsFilter) {
            CorsSource corsSource = CorsSource.from(properties);
            source.setCorsSource(corsSource);
        }
        return source;
    }

    /* ********************************************************************************************
     *
     *         私有逻辑
     *
     * *********************************************************************************************
     * */

    @Note("分隔,看看是否存在多个，读取出来返回为 Set")
    private static Set<String> private_stringSplitToSet(String s) {
        if(s == null || s.isEmpty()) return null;
        String[] a = s.split(",");
        int length = a.length;
        if(length == 0) return null;
        Set<String> result = new HashSet<>((int) ((a.length / 0.75) + 1));
        for (String ip : a) {
            if(ip != null && ip.isEmpty()) result.add(ip);
        }
        return result;
    }

    @Note("读取文件，将里面的每一行读出来，替换前缀等处理后，返回为 Set")
    private static Set<String> private_readFileAsSet(String filePath, String prefix) {
        if(filePath == null || filePath.isEmpty()) return null;
        File file = new File(filePath);
        if(!file.exists()) return null;
        BufferedReader reader = null;
        try {
            reader = IOKit.bufferedReader(file);
            String ip;
            Set<String> result = new HashSet<>();
            while ((ip = reader.readLine()) != null) {
                if(ip.isEmpty()) continue;
                if(prefix != null) {
                    result.add(ip.replace(prefix, ""));
                    continue;
                }
                result.add(ip);
            }
            return result;
        } catch (IOException e) {
            throw new IORuntimeException(e);
        } finally {
            IOKit.close(reader);
        }
    }

    @Note("将数组内的全部转为 Set")
    private static Set<String> private_arrayToSet(String[] a) {
        if(a == null) return null;
        int length = a.length;
        if(length == 0) return null;
        Set<String> result = new HashSet<>((int) ((length / 0.75) + 1));
        for (String ip : a) {
            if(ip != null && ip.isEmpty()) result.add(ip);
        }
        return result;
    }

    @Note("将可迭代容器内的全部转为 Set")
    private static Set<String> private_iterableToSet(Iterable<String> iterable) {
        int size = RowKit.getIterableSize(iterable);
        if(size != 0) {
            Set<String> result = new HashSet<>((int) ((size / 0.75) + 1));
            for (String ip : iterable) {
                if(ip != null && ip.isEmpty()) result.add(ip);
            }
            return result;
        }
        return null;
    }

    @Note("对读取出来的 URI 做进一步处理")
    private void private_forceDisabledURISet(Set<String> uriSet) {
        if(uriSet != null && !uriSet.isEmpty()) {
            String contextPath = ProfileKit.getServerContextPath();
            this.disableURIs = new HashSet<>((int) ((uriSet.size() / 0.75) + 1));
            for(String uri : uriSet) {
                this.disableURIs.add(WebKit.uriAddServerContextPath(uri, contextPath));
            }
            this.serverContextPath = contextPath;
        }
    }
}
